/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.ui.parts;

import com.architexa.org.eclipse.draw2d.ExclusionSearch;
import com.architexa.org.eclipse.draw2d.IFigure;
import com.architexa.org.eclipse.draw2d.LightweightSystem;
import com.architexa.org.eclipse.draw2d.geometry.Point;
import com.architexa.org.eclipse.gef.AccessibleEditPart;
import com.architexa.org.eclipse.gef.EditDomain;
import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.EditPartViewer;
import com.architexa.org.eclipse.gef.ExposeHelper;
import com.architexa.org.eclipse.gef.GraphicalEditPart;
import com.architexa.org.eclipse.gef.GraphicalViewer;
import com.architexa.org.eclipse.gef.Handle;
import com.architexa.org.eclipse.gef.LayerConstants;
import com.architexa.org.eclipse.gef.MouseWheelHandler;
import com.architexa.org.eclipse.gef.MouseWheelHelper;
import com.architexa.org.eclipse.gef.RootEditPart;
import com.architexa.org.eclipse.gef.editparts.LayerManager;
import com.architexa.org.eclipse.gef.editparts.ScalableRootEditPart;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;

import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.util.Assert;



/**
 * An EditPartViewer implementation based on {@link com.architexa.org.eclipse.draw2d.IFigure Figures}.
 * @author hudsonr
 */
public class GraphicalViewerImpl
	extends AbstractEditPartViewer
	implements GraphicalViewer
{

private final LightweightSystem lws = createLightweightSystem();
IFigure rootFigure;
private DomainEventDispatcher eventDispatcher;
private FocusListener lFocus;

/**
 * Constructs a GraphicalViewerImpl with the default root editpart.
 */
public GraphicalViewerImpl() {
	createDefaultRoot();
	setProperty(MouseWheelHandler.KeyGenerator.getKey(SWT.NONE), 
			MouseWheelDelegateHandler.SINGLETON);
}

/**
 * @see com.architexa.org.eclipse.gef.EditPartViewer#createControl(org.eclipse.swt.widgets.Composite)
 */
public Control createControl(Composite composite) {
	setControl(new Canvas(composite, SWT.NO_BACKGROUND));
	return getControl();
}

/**
 * Creates the default root editpart. Called during construction.
 */
protected void createDefaultRoot() {
	setRootEditPart(new ScalableRootEditPart());
}

/**
 * Creates the lightweight system used to host figures. Subclasses should not need to
 * override this method.
 * @return the lightweight system
 */
protected LightweightSystem createLightweightSystem() {
	return new LightweightSystem();
}

/**
 * @see AbstractEditPartViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
 */
protected void handleDispose(DisposeEvent e) {
	super.handleDispose(e);
	getLightweightSystem().getUpdateManager().dispose();
}

/**
 * This method is invoked when this viewer's control gains focus.  It gives focus to the
 * {@link AbstractEditPartViewer#focusPart focusPart}, if there is one.
 * @param fe the focusEvent received by this viewer's control
 */
protected void handleFocusGained(FocusEvent fe) {
	if (focusPart != null)
		focusPart.setFocus(true);
}

/**
 * This method is invoked when this viewer's control loses focus.  It removes focus from
 * the {@link AbstractEditPartViewer#focusPart focusPart}, if there is one.
 * @param fe the focusEvent received by this viewer's control
 */
protected void handleFocusLost(FocusEvent fe) {
	if (focusPart != null)
		focusPart.setFocus(false);
}

/**
 * @see GraphicalViewer#findHandleAt(com.architexa.org.eclipse.draw2d.geometry.Point)
 */
public Handle findHandleAt(Point p) {
	LayerManager layermanager = (LayerManager)getEditPartRegistry().get(LayerManager.ID);
	if (layermanager == null)
		return null;
	List list = new ArrayList(3);
	list.add(layermanager.getLayer(LayerConstants.PRIMARY_LAYER));
	list.add(layermanager.getLayer(LayerConstants.CONNECTION_LAYER));
	list.add(layermanager.getLayer(LayerConstants.FEEDBACK_LAYER));
	IFigure handle = getLightweightSystem().getRootFigure()
		.findFigureAtExcluding(p.x, p.y, list);
	if (handle instanceof Handle)
		return (Handle)handle;
	return null;
}

/**
 * @see EditPartViewer#findObjectAtExcluding(Point, Collection, EditPartViewer.Conditional)
 */
public EditPart findObjectAtExcluding(
	Point pt,
	Collection exclude,
	final Conditional condition) {
	class ConditionalTreeSearch extends ExclusionSearch {
		ConditionalTreeSearch (Collection coll) {
			super(coll);
		}
		public boolean accept(IFigure figure) {
			EditPart editpart = null;
			while (editpart == null && figure != null) {
				editpart = (EditPart)getVisualPartMap().get(figure);
				figure = figure.getParent();
			}
			return editpart != null
				&& (condition == null || condition.evaluate(editpart));
		}
	}
	IFigure figure = getLightweightSystem()
		.getRootFigure()
		.findFigureAt(pt.x, pt.y, new ConditionalTreeSearch(exclude));
	EditPart part = null;
	while (part == null && figure != null) {
		part = (EditPart)getVisualPartMap().get(figure);
		figure = figure.getParent();
	}
	if (part == null)
		return getContents();
	return part;
}

/**
 * Flushes and pending layouts and paints in the lightweight system.
 * @see com.architexa.org.eclipse.gef.EditPartViewer#flush()
 */
public void flush() {
	getLightweightSystem().getUpdateManager().performUpdate();
}

/**
 * Returns the event dispatcher
 * @deprecated This method should not be called by subclasses
 * @return the event dispatcher
 */
protected DomainEventDispatcher getEventDispatcher() {
	return eventDispatcher;
}

/**
 * Convenience method for finding the layer manager.
 * @return the LayerManager
 */
protected LayerManager getLayerManager() {
	return (LayerManager)getEditPartRegistry().get(LayerManager.ID);
}

/**
 * Returns the lightweight system.
 * @return the system
 */
public LightweightSystem getLightweightSystem() {
	return lws;
}

/**
 * Returns the root figure
 * @deprecated There is no reason to call this method
 * $TODO delete this method
 * @return the root figure
 */
protected IFigure getRootFigure() {
	return rootFigure;
}

/**
 * Extended to flush paints during drop callbacks.
 * @see com.architexa.org.eclipse.gef.ui.parts.AbstractEditPartViewer#hookDropTarget()
 */
protected void hookDropTarget() {
	//Allow the real drop targets to make their changes first.
	super.hookDropTarget();
	
	//Then force and update since async paints won't occurs during a Drag operation
	getDropTarget().addDropListener(new DropTargetAdapter() {
		public void dragEnter(DropTargetEvent event) {
			flush();
		}
		public void dragLeave(DropTargetEvent event) {
			flush();
		}
		public void dragOver(DropTargetEvent event) {
			flush();
		}
	});
}

/**
 * 
 * Extended to tell the lightweight system what its control is.
 * @see com.architexa.org.eclipse.gef.ui.parts.AbstractEditPartViewer#hookControl()
 */
protected void hookControl() {
	super.hookControl();
	getLightweightSystem().setControl((Canvas)getControl());
	getControl().addFocusListener(lFocus = new FocusListener() {
		public void focusGained(FocusEvent e) {
			handleFocusGained(e);
		}
		public void focusLost(FocusEvent e) {
			handleFocusLost(e);
		}
	});
}

/**
 * Registers the accessible editpart with the event dispatcher.
 * @param acc the accessible
 */
public void registerAccessibleEditPart(AccessibleEditPart acc) {
	Assert.isNotNull(acc);
	getEventDispatcher().putAccessible(acc);
}

/**
 * Reveals the specified editpart by using {@link ExposeHelper}s. A bottom-up scan through
 * the parent-chain is performed, looking for expose helpers along the way, and asking
 * them to expose the given editpart.
 * @see com.architexa.org.eclipse.gef.EditPartViewer#reveal(EditPart)
 */
public void reveal(EditPart part) {
	if (part == null)
		return;
	EditPart current = part.getParent();
	while (current != null) {
		ExposeHelper helper = (ExposeHelper)current.getAdapter(ExposeHelper.class);
		if (helper != null)
			helper.exposeDescendant(part);
		current = current.getParent();
	}
	AccessibleEditPart acc = (AccessibleEditPart)part.getAdapter(AccessibleEditPart.class);
	if (acc != null)
		getControl().getAccessible().setFocus(acc.getAccessibleID());
}

/**
 * Extended implementation to flush the viewer as the context menu is shown.
 * @see EditPartViewer#setContextMenu(org.eclipse.jface.action.MenuManager)
 */
public void setContextMenu(MenuManager contextMenu) {
	super.setContextMenu(contextMenu);
	if (contextMenu != null)
		contextMenu.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				flush();
			}
		});
}

/**
 * @see com.architexa.org.eclipse.gef.EditPartViewer#setCursor(org.eclipse.swt.graphics.Cursor)
 */
public void setCursor(Cursor newCursor) {
	if (getEventDispatcher() != null)
		getEventDispatcher().setOverrideCursor(newCursor);
}

/**
 * Extends the drag source to handle figures which handle MouseDown events, thereby
 * aborting any DragDetect callbacks.
 * @see AbstractEditPartViewer#setDragSource(org.eclipse.swt.dnd.DragSource)
 */
protected void setDragSource(DragSource source) {
	super.setDragSource(source);

	class TheLastListener extends DragSourceAdapter {
		public void dragStart(DragSourceEvent event) {
			// If the EventDispatcher has captured the mouse, don't perform native drag.
			if (getEventDispatcher().isCaptured())
				event.doit = false;
			if (event.doit) {
				//A drag is going to occur, tell the EditDomain
				getEventDispatcher().dispatchNativeDragStarted(event,
					GraphicalViewerImpl.this);
				/* 
				 * The mouse down that came before the dragstart, or the dragstart event
				 * itself, may have caused selection or something that needs to be
				 * painted. paints will not get processed during DND, so flush.
				 */
				flush();
			}
		}
		public void dragFinished(DragSourceEvent event) {
			getEventDispatcher()
				.dispatchNativeDragFinished(event, GraphicalViewerImpl.this);
		}
	}
	
	/*
	 * The DragSource may be set to null if there are no listeners.  If there are
	 * listeners, this should be *the* last listener because all other listeners are
	 * hooked in super().
	 */
	if (source != null)
		getDragSource().addDragListener(new TheLastListener());
}

/**
 * @see com.architexa.org.eclipse.gef.EditPartViewer#setEditDomain(com.architexa.org.eclipse.gef.EditDomain)
 */
public void setEditDomain(EditDomain domain) {
	super.setEditDomain(domain);
	// Set the new event dispatcher, even if the new domain is null.  This will dispose
	// the old event dispatcher.
	getLightweightSystem()
		.setEventDispatcher(eventDispatcher = new DomainEventDispatcher(domain, this));
}

/**
 * @see com.architexa.org.eclipse.gef.EditPartViewer#setRootEditPart(com.architexa.org.eclipse.gef.RootEditPart)
 */
public void setRootEditPart(RootEditPart editpart) {
	super.setRootEditPart(editpart);
	setRootFigure(((GraphicalEditPart)editpart).getFigure());
}

/**
 * Sets the lightweight system's root figure.
 * @param figure the root figure
 */
protected void setRootFigure(IFigure figure) {
	rootFigure = figure;
	getLightweightSystem().setContents(rootFigure);
}

/**
 * @see com.architexa.org.eclipse.gef.EditPartViewer#setRouteEventsToEditDomain(boolean)
 */
public void setRouteEventsToEditDomain(boolean value) {
	getEventDispatcher().setRouteEventsToEditor(value);
}

/**
 * @see com.architexa.org.eclipse.gef.ui.parts.AbstractEditPartViewer#unhookControl()
 */
protected void unhookControl() {
	super.unhookControl();
	if (lFocus != null) {
		getControl().removeFocusListener(lFocus);
		lFocus = null;
	}
}

/**
 * @see EditPartViewer#unregisterAccessibleEditPart(com.architexa.org.eclipse.gef.AccessibleEditPart)
 */
public void unregisterAccessibleEditPart(AccessibleEditPart acc) {
	Assert.isNotNull(acc);
	getEventDispatcher().removeAccessible(acc);
}

private static class MouseWheelDelegateHandler
	implements MouseWheelHandler
{
	private static final MouseWheelHandler SINGLETON = new MouseWheelDelegateHandler();
	private MouseWheelDelegateHandler() { }
	/**
	 * Delegates handling to the selected editpart's MouseWheelHelper.
	 * @see com.architexa.org.eclipse.gef.MouseWheelHandler#handleMouseWheel(org.eclipse.swt.widgets.Event, com.architexa.org.eclipse.gef.EditPartViewer)
	 */
	public void handleMouseWheel(Event event, EditPartViewer viewer) {
		EditPart part = viewer.getFocusEditPart();
		do {
			MouseWheelHelper helper = (MouseWheelHelper)part
					.getAdapter(MouseWheelHelper.class);
			if (helper != null)
				helper.handleMouseWheelScrolled(event);
			part = part.getParent();
		} while (event.doit && part != null);
	}
}

}
